#pragma once
#include "point.hpp"
#include "path.hpp"
#include "map.hpp"
#include "agent.hpp"

#include <string>
#include <unordered_map>
#include <iostream>

#include <nlohmann/json.hpp>

namespace dw 
{
    class Scheduler
    {
        Map::State map;
        std::unordered_map<ID, std::shared_ptr<Path>> paths;

    public:
        Scheduler() {/* done */}
        Scheduler(const Map::State& state) : map(state) { /* done */ }
        ~Scheduler() { /* done */ }

    public:
        std::unordered_map<ID, std::shared_ptr<Path>>::iterator begin() 
        {
            return paths.begin();
        }

        std::unordered_map<ID, std::shared_ptr<Path>>::const_iterator begin() const
        {
            return paths.begin();
        }

        std::unordered_map<ID, std::shared_ptr<Path>>::iterator end()
        {
            return paths.end();
        }

        std::unordered_map<ID, std::shared_ptr<Path>>::const_iterator end() const
        {
            return paths.end();
        }

        bool Exists(ID id) const
        {
            auto iter = paths.find(id);
            return !(iter == paths.end());
        }

    public:
        bool loadMap(const std::string &path) {
            Map m;
            bool flag = m.load(path);
            map = m.getMapState();
            return flag;
        }

        bool setMap(const Map::State &state) {
            map = state;
            return true;
        }

    public:
        bool setStartPoint(ID id, const Coord start) {
            if (!Exists(id)) return false;
            if (map.isBlock(start)) return false;
            paths[id]->push_front(start);
            return true;
        }

        bool setDestination(ID id, const Coord destination) {
            if (!Exists(id)) return false;
            if (map.isBlock(destination)) return false;
            paths[id]->push_back(destination);
            return true;
        }

        std::shared_ptr<Path> FindPath(ID id)
        {
            if (!Exists(id)) return nullptr;

            if (paths[id]->length() < 2) return nullptr;

            if ((*paths[id])(map, paths[id]->start(), paths[id]->destination(), 12, 5)) {
                paths[id]->simplify(map);
                paths[id]->interpolate(0.15);
                return paths[id];
            }

            paths.erase(id);
            return nullptr;
        }

        std::shared_ptr<Path> FindPath(ID id, const Coord &pos, const Coord destination)
        {
            if (map.isBlock(pos) || map.isBlock(destination)) return nullptr;

            std::shared_ptr<Path> p = std::make_shared<Path>();
            if ((*p)(map, pos, destination, 12, 5)) {
                p->simplify(map);
                p->interpolate(0.15);
                paths.insert_or_assign(id, p);
                return p;
            } else {
                return nullptr;
            }
        }

        std::shared_ptr<Path> FindPath(const Agent::State &state, const Coord destination) {
            if (!Exists(state.id)) {
                paths.insert({state.id, std::move(std::make_shared<Path>())});
            }
            setStartPoint(state.id, state.vPosition);
            setDestination(state.id, destination);
            return FindPath(state.id);
        }

        std::shared_ptr<Path> getPath(ID id) {
            return paths[id];
        }
        
        bool DeletePath(ID id)
        {
            if (Exists(id))
            {
                paths[id].reset();
            }

            return paths.erase(id);
        }

    public:
        friend void to_json(nlohmann::json &j, const Scheduler &scheduler);
        friend void from_json(const nlohmann::json &j, Scheduler &scheduler);

        std::string encode() const {
            nlohmann::json j;
            nlohmann::json& p= j["paths"];
            to_json(p, *this);
            return j.dump();
        }

        Scheduler &decode(const std::string &encoded) {
            nlohmann::json j = nlohmann::json::parse(encoded);
            nlohmann::json &p = j["paths"];
            from_json(p, *this);
            return *this;
        }

        friend std::ostream &operator<<(std::ostream &os, const Scheduler &scheduler) {
            os << scheduler.encode();
            return os;
        }
    };

    void to_json(nlohmann::json &j, const Scheduler &scheduler)
    {
        j = nlohmann::json::array();
        for (auto pair : scheduler)
        {
            nlohmann::json path;
            if (nullptr != pair.second) {
                Path p = *pair.second;
                p.simplify(scheduler.map);
                to_json(path, p);
                path["id"] = pair.first;
                j += path;
            }
        }
    }

    void from_json(const nlohmann::json &j, Scheduler &scheduler)
    {
        for (auto p : j)
        {
            ID id = p["id"];
            if (scheduler.Exists(id)) {
                std::shared_ptr<Path> path = scheduler.getPath(id);
                from_json(p, *path);
            } else {
                std::shared_ptr<Path> path = std::make_shared<Path>();
                from_json(p, *path);
                scheduler.paths.insert({id, std::move(path)});
            }
        }
    }
}